{

This file is used to modify sprite animation data.

There are two tables, the sprite table and frame table.

We use a dumb mark/search system to allocate and delete entries from the tables.  If the
first cell is -1, then the entry is free/invalid.  All of the entries up to the last
"used" entry are stored in the file, which can waste some space but is required to keep
entries in the proper places and keeps things simple.

An entry in the sprite table defines the name (13 chars max) and first frame of an animation.
A sprite can be one frame and that will just be a stationary sprite, or it can be up to the
maximum number of frames (MAX-FRAMES).

Frames define the rectangles where the graphics reside in a texture that is associated with
this sprite set (but that will be defined elsewhere).  A frame is a kind of Subimage; the
last two words distinguish it by storing "delay" and "jump" parameters.  By jumping to frames
you chain together animations and optionally create loops.  A bonus of this scheme is that
you don't have to make the animation loop to the first frame.

}


Requires Stacks
Requires Tools


1000 Constant max-frames

: /fill   here over allot swap 255 fill ;

\ starting sprite # (2), counted name(max 13)
Create sprites
   16 max-frames * /fill

Create frametable
   /Subimage max-frames * /fill

: sprite[]   /subimage * sprites + ;
: frame[]   16 * frametable + ;

: active?   @ 0< not ;

\ Frames

: init-frame   ( x y w h frame# -- )
   >r
   r@ frame[] 4h!     \ initialize rectangle
   1 r@ frame[] 12 + h! \ initialize delay
   r> frame[] 8 + off \ initialize offset
   ;

: new-frame
   max-frames 0 do
      i frame[] @ 0< if i frame[] off  i unloop exit then
   loop  abort" Error: Out of free frames in frame table!" ;

: next-frame   frame[] 14 + h@ ;

: create-frame   ( x y w h -- frame# )
   new-frame >r
   r@ init-frame
   r> ;

: next-frame! ( frame# dest# -- )
   frame[] 14 + h! ;

:macro !origin   8 + 2h! ;

: ->delay
   12 + ;

: delay@   ->delay h@ $3fff and ;
: !delay   dup ->delay h@ $c000 and rot or swap ->delay h! ;
: !flip   dup @delay rot 14 lshift or swap ->delay h! ;

: insert-frame    ( frame# dest# -- )
   dup next-frame dup 0< not if third next-frame! else drop then   next-frame! ;

: free-frame
   frame[] 16 255 fill ;


\ Sprites
: ->name   2 + ;

: sprite-name@   sprite[] ->name count ;

: first-frame    sprite[] h@ ;

: delete-sprite   sprite[] on ;

: new-sprite
   max-frames 0 do
      i sprite[] @ 0< if i sprite[] off  i unloop exit then
   loop  abort" Error: Out of free sprites in sprite table!" ;

: create-sprite   ( string count frame# -- sprite# )
   new-sprite >r
   r@ sprite[] h!
   13 min r@ sprite[] ->name place
   r> ;

\ If non looping, last frame jumps to -1, if looping, last frame jumps to start of loop, which this will detect.
1000 Stack: framestack

: (in-framestack)
   framestack stack-count 0 ?do @+ third = if unloop 2drop true exit then loop 2drop false ;

: last-frame  ( sprite# -- frame# )
   framestack clear-stack
   first-frame begin
      dup framestack push
      dup  next-frame
      dup  -1 = if drop exit then
      dup  (in-framestack) if drop exit then
      nip
   again ;


: previous-sprite  ( sprite# -- sprite# )
   begin  1 - 0 max-frames wrap dup sprite[] active? until ;
: next-sprite  ( sprite# -- sprite# )
   begin  1 + 0 max-frames wrap dup sprite[] active? until ;

: sprite-length  ( sprite# -- n )
   dup last-frame locals| last |
   1 swap first-frame begin
      dup last <> while
      1 u+ next-frame
   repeat drop ;

: (delete-next-frame)
   dup next-frame dup next-frame locals| c b a |
   c a next-frame!
   b free-frame ;

: find-frame   ( sprite# index -- frame# )
   swap first-frame swap  0 ?do  next-frame loop ;

: delete-frame  ( sprite# index -- )
   locals| index sprite |
   sprite sprite-length 1 = ?exit
   index 0= if
      \ First frame
      sprite index find-frame dup
         next-frame sprite sprite[] h!
         free-frame
   else
      sprite index 1 - find-frame (delete-next-frame)
   then ;

: clear-spriteset
   sprites   16 max-frames *  255 fill
   frametable    /Subimage max-frames *  255 fill ;

: spritetable-length
   0 max-frames 1 - do i sprite[] active? if i 1 + unloop exit then -1 +loop ;

: frametable-length
   0 max-frames 1 - do i frame[] active? if i 1 + unloop exit then -1 +loop ;

\ Format:  headersize , #frames , #sprites , 0 , data ...
-? 0 value #1
: save-spriteset  ( string count -- )
   file-out:[
      4 cells ^   \ header size
      frametable-length ^
      spritetable-length ^
      0 ^
      frametable frametable-length /subimage * write
      sprites spritetable-length 16 * write
   ;]  ;

: load-spriteset  ( string count -- )
   clear-spriteset
   file:[ pad -rot read-file throw drop
      \ pad 128 dump
      pad dup @ +  frametable  pad cell+ @  /subimage *  move
      pad dup @ +  pad cell+ @ /subimage * +  sprites  pad cell+ cell+ @  16 *  move
   ;] ;


\ Test
\\
0 0 16 16 create-frame constant f
s" test" f create-sprite constant s
\ s last-frame


